/*
 * Copyright © 2017 eqxiu.com 北京中网易企秀科技有限公司  All rights reserved.
 */

package cn.hermit.data.oson;

import cn.hermit.data.json.model.JSONArray;
import cn.hermit.data.json.model.JSONDocument;
import cn.hermit.data.json.model.JSONFactory;
import cn.hermit.data.json.model.JSONObject;
import cn.hermit.data.json.model.JSONValue;
import cn.hermit.util.ReflectionUtils;
import cn.hermit.data.json.factory.OrderedJSONFactory;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;

/**
 * @author JackGao 高建国
 * @description //TODO 设计说明
 * @date 1/6/2017
 */
public class OSON {

    public static JSONDocument convertToJSON(Object obj) {
        return convertToJSON(obj, new OrderedJSONFactory());
    }

    private static JSONDocument convertToJSON(Object obj, JSONFactory jsonFactory) {
        JSONDocument jsonDocument = null;
        if (obj == null) {
            jsonDocument = jsonFactory.createValue(null);
            return jsonDocument;
        }
        Class<?> clz = obj.getClass();
        if (clz.isArray()) {
            jsonDocument = jsonFactory.createArray();
            int length = Array.getLength(obj);
            if (length == 0) {
                return jsonDocument;
            }
            for (int i = 0; i < length; i++) {
                jsonFactory.arrayValue((JSONArray) jsonDocument, convertToJSON(Array.get(obj, i), jsonFactory));
            }
        } else if (Collection.class.isAssignableFrom(clz)) {
            jsonDocument = jsonFactory.createArray();
            Collection col = (Collection) obj;
            if (col.size() == 0) {
                return jsonDocument;
            }
            Iterator iterator = col.iterator();
            while (iterator.hasNext()) {
                jsonFactory.arrayValue((JSONArray) jsonDocument, convertToJSON(iterator.next(), jsonFactory));
            }
        } else if (Map.class.isAssignableFrom(clz)) {
            Map map = (Map) obj;
            jsonDocument = jsonFactory.createObject();
            if (map.size() == 0) {
                return jsonDocument;
            }
            for (Object key : map.keySet()) {
                jsonFactory.objectEntry((JSONObject) jsonDocument, key.toString(), convertToJSON(map.get(key), jsonFactory));
            }
        } else if (ReflectionUtils.isBaseType(clz)) {
            jsonDocument = jsonFactory.createValue(obj, false);
            return jsonDocument;
        } else if (String.class == clz || Date.class == clz) {
            jsonDocument = jsonFactory.createValue(obj);
            return jsonDocument;
        } else {
            jsonDocument = jsonFactory.createObject();
            Field[] fields = clz.getDeclaredFields();
            for (Field field : fields) {
                int modifiers = field.getModifiers();
                if (Modifier.isStatic(modifiers)) {
                    continue;
                }
                boolean originAccessible = field.isAccessible();
                try {
                    field.setAccessible(true);
                    Object result = field.get(obj);
                    jsonFactory.objectEntry((JSONObject) jsonDocument, field.getName(), convertToJSON(result, jsonFactory));
                } catch (ReflectiveOperationException e) {
                    e.printStackTrace();
                    //ignored
                } finally {
                    field.setAccessible(originAccessible);
                }
            }
        }
        return jsonDocument;
    }

    public static Object convertToObject(JSONDocument jsonDocument, Class<?> clz, Type genericType) {
        Object obj = null;
        try {
            if (clz.isArray()) {
                if (!JSONArray.class.isInstance(jsonDocument)) {
                    return null;
                }
                JSONArray jsonArray = (JSONArray) jsonDocument;
                obj = Array.newInstance(clz.getComponentType(), jsonArray.size());
                Iterator<JSONDocument> iterator = jsonArray.valueIterator();
                int i = 0;
                while (iterator.hasNext()) {
                    JSONDocument jsonDoc = iterator.next();
                    Object subObj = convertToObject(jsonDoc, clz.getComponentType(), null);
                    Array.set(obj, i++, subObj);
                }
            } else if (Collection.class.isAssignableFrom(clz)) {
                if (!JSONArray.class.isInstance(jsonDocument)) {
                    return null;
                }
                obj = clz.newInstance();
                Collection col = (Collection)obj;
                JSONArray jsonArray = (JSONArray) jsonDocument;
                Iterator<JSONDocument> iterator = jsonArray.valueIterator();
                while (iterator.hasNext()) {
                    JSONDocument jsonDoc = iterator.next();
                    Object subObj = convertToObject(jsonDoc, clz.getComponentType(), null);
                    col.add(subObj);
                }
            } else if(ReflectionUtils.isBaseType(clz) || String.class == clz || Date.class == clz){
                if (!JSONValue.class.isInstance(jsonDocument)) {
                    return null;
                }
                obj = ReflectionUtils.guessObject(jsonDocument.toString());
            } else {
                if (!JSONObject.class.isInstance(jsonDocument)) {
                    return null;
                }
                JSONObject jsonObject = (JSONObject) jsonDocument;
                obj = clz.newInstance();
                Field[] fields = clz.getFields();
                for (Field field : fields) {
                    int modifiers = field.getModifiers();
                    if (Modifier.isStatic(modifiers)) {
                        continue;
                    }
                    boolean originAccessible = field.isAccessible();
                    try {
                        field.setAccessible(true);
                        field.set(obj, convertToObject(jsonObject.getValue(field.getName()), field.getType(), field.getGenericType()));
                    } catch (ReflectiveOperationException e) {
                        //ignored
                    } finally {
                        field.setAccessible(originAccessible);
                    }
                }
            }
            return obj;
        } catch (Exception e) {
            //ignored
            return null;
        }
    }

}
